/*  Copyright © 2009 Blake Hegerle -*- C++ -*- */

/*  This file is part of metal.
 
    Metal is free software: you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.
 
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see
    <http://www.gnu.org/licenses/>.

*/

#pragma once

#include <map>

#include "drivers/idriver.h"
#include "meta/binding.h"
#include "meta/proto.h"
#include "meta/function.h"
#include "decl.h"
#include "def.h"
#include "meta/module.h"
#include "meta/set.h"
#include "function.h"

namespace metal {
  template<
    typename Functions = meta::bindings<>,
    typename Definitions = meta::set<>
    >
  class module {
    typedef std::map<unsigned int, function_ptr> table_t;
    typedef std::shared_ptr<table_t> table_ptr;
  public:
    module(const std::string &name, 
	   table_ptr table = table_ptr(new table_t))
      : name(name), table(table) {}

    template<
      typename Type, 
      typename Ident, 
      typename... Args>
    typename meta::module_declaration_binder<
      Functions, 
      Definitions, 
      Type, 
      Ident, 
      Args...
    >::type
    operator,(declaration<Type, Ident, Args...> decl) {
      typedef meta::module_declaration_binder<
        Functions, 
        Definitions, 
	Type, 
	Ident, 
	Args...
      > binder;
      
      static_assert(binder::compatible, 
		    "function cannot be redeclared "
		    "with a different signature");

      return typename binder::type(name);
    }
    
    template<
      typename Ident, 
      typename MetaExpr, 
      typename... Args
    >
    typename meta::module_definition_binder<
      Functions, 
      Definitions, 
      Ident,
      MetaExpr,
      Args...
    >::type
    operator,(const assignment<true, Ident, meta::lambda_expr<void, MetaExpr, Args...> > &defun) {
      typedef meta::module_definition_binder<
        Functions, 
        Definitions, 
        Ident,
        MetaExpr,
        Args...
      > binder;
      
      static_assert(binder::compatible,
		    "function cannot be defined with a different signature");
      static_assert(!binder::duplicate,
		    "function has already been defined");
      static_assert(binder::is_safe,
		    "the function body is not type-safe");
      static_assert(binder::is_bound, 
		    "the function body mentions undefined identifiers");
      
      auto fid = id<Ident>();
      
      if(table->find(fid) == table->end()) {
	typedef typename binder::check::scope s;
	typedef typename binder::return_type rt;

	(*table)[fid] = convert<s, rt>(defun);
      }
      
      return typename binder::type(name, table);
    }
    
    template<
      typename Type,
      typename Ident, 
      typename MetaExpr, 
      typename... Args
    >
    typename meta::module_recursive_definition_binder<
      Functions, 
      Definitions, 
      Type,
      Ident,
      MetaExpr,
      Args...
    >::type
    operator,(const assignment<true, Ident, meta::lambda_expr<Type, MetaExpr, Args...> > &defun) {
      typedef meta::module_recursive_definition_binder<
        Functions, 
        Definitions, 
	Type,
        Ident,
        MetaExpr,
        Args...
      > binder;
      
      static_assert(binder::compatible,
		    "function cannot be defined with a different signature");
      static_assert(!binder::duplicate,
		    "function has already been defined");
      static_assert(binder::is_safe,
		    "the function body is not type-safe");
      static_assert(binder::is_bound, 
		    "the function body mentions undefined identifiers");
      
      auto fid = id<Ident>();
      
      if(table->find(fid) == table->end()) {
	typedef typename binder::check::scope s;
	
	(*table)[fid] = convert<s, Type>(defun);
      }
      
      return typename binder::type(name, table);
    }

    void emit(driver::idriver &d) const {
      // decls
      auto e = table->end();

      for(auto i = table->begin(); i != e; i++) {
	auto f = i->second;

	std::vector<param_impl> params(f->begin(), f->end());
	d.emit_declaration(f->type(), f->name(), params);
      }

      // defuns
      for(auto i = table->begin(); i != e; i++) {
	auto f = i->second;

	std::vector<param_impl> params(f->begin(), f->end());
	d.emit_definition(f->name(), f->body(), params);
      }
    }
  private:
    std::string name;
    table_ptr table;
  };
}
